﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace MvcToolbox
{
    /// <summary>
    /// Class enumeration DropDownListHelper
    /// </summary>
    public static class DropDownListExtensions
    {
        /// <summary>
        /// The name cache
        /// </summary>
        private static readonly Dictionary<Enum, string> NameCache = new Dictionary<Enum, string>();

        /// <summary>
        /// Drops down list for enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TProperty">The type of the T property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <returns>DropDownList result.</returns>
        public static MvcHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        {
            return htmlHelper.DropDownListForEnum(expression, null, null);
        }

        /// <summary>
        /// Drops down list for enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TProperty">The type of the T property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <returns>DropDownList result.</returns>
        public static MvcHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            return htmlHelper.DropDownListForEnum(expression, new RouteValueDictionary(htmlAttributes));
        }

        /// <summary>
        /// Drops down list for enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TProperty">The type of the T property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <returns>DropDownList as string</returns>
        public static MvcHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
        {
            return htmlHelper.DropDownListForEnum(expression, null, htmlAttributes);
        }

        /// <summary>
        /// Drops down list for enumeration
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TProperty">The type of the T property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="optionLabel">The option label.</param>
        /// <returns>DropDownList as string</returns>
        public static MvcHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string optionLabel)
        {
            return htmlHelper.DropDownListForEnum(expression, optionLabel, null);
        }

        /// <summary>
        /// Drops down list for enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TProperty">The type of the T property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="optionLabel">The option label.</param>
        /// <param name="useCustomLocalisation">if set to <c>true</c> [use custom localization].</param>
        /// <returns>DropDownList as string</returns>
        /// <exception cref="System.ArgumentNullException">
        /// expression
        /// or
        /// expression
        /// </exception>
        public static MvcHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string optionLabel, bool useCustomLocalisation)
        {
            if (!useCustomLocalisation)
            {
                return htmlHelper.DropDownListForEnum(expression, optionLabel, null);
            }

            var member = expression.Body as MemberExpression;

            var selectedValue = string.Empty;
            var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            if (metadata.Model != null)
            {
                selectedValue = metadata.Model.ToString();
            }

            var enumType = Nullable.GetUnderlyingType(member.Type) ?? member.Type;

            var listItems = new List<SelectListItem>();
            foreach (var name in Enum.GetNames(enumType))
            {
                Enum type = Enum.Parse(enumType, name) as Enum;
                string text = htmlHelper.Label(type.GetName()).ToString();
                if (!string.IsNullOrEmpty(text) && text != "null")
                {
                    listItems.Add(new SelectListItem
                    {
                        Text = text,
                        Value = name,
                        Selected = string.IsNullOrEmpty(optionLabel) ? name == selectedValue : false
                    });
                }
            }

            return htmlHelper.DropDownListFor(expression, listItems, optionLabel, null);
        }

        /// <summary>
        /// Drops down list for enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TProperty">The type of the T property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="optionLabel">The option label.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <returns>DropDownList as string</returns>
        public static MvcHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string optionLabel, object htmlAttributes)
        {
            return htmlHelper.DropDownListForEnum(expression, optionLabel, new RouteValueDictionary(htmlAttributes), true);
        }

        /// <summary>
        /// Drops down list for enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the T model.</typeparam>
        /// <typeparam name="TProperty">The type of the T property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="optionLabel">The option label.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <param name="useSelectValue">if set to <c>true</c> [use select value].</param>
        /// <returns>DropDownList as string</returns>
        /// <exception cref="System.ArgumentNullException">
        /// expression
        /// or
        /// expression
        /// </exception>
        public static MvcHtmlString DropDownListForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string optionLabel, IDictionary<string, object> htmlAttributes, bool useSelectValue)
        {
            var member = expression.Body as MemberExpression;
            var selectedValue = string.Empty;
            var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            if (metadata.Model != null)
            {
                selectedValue = metadata.Model.ToString();
            }

            var enumType = Nullable.GetUnderlyingType(member.Type) ?? member.Type;

            var listItems = new List<SelectListItem>();

            if (useSelectValue)
            {
                listItems.Add(new SelectListItem() { Text = "Select...", Value = "" });
            }

            foreach (var name in Enum.GetNames(enumType))
            {
                Enum type = Enum.Parse(enumType, name) as Enum;
                listItems.Add(new SelectListItem
                {
                    Text = type.GetName(),
                    Value = name,
                    Selected = string.IsNullOrEmpty(optionLabel) ? name == selectedValue : false
                });
            }

            return htmlHelper.DropDownListFor(expression, listItems, optionLabel, htmlAttributes);
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>Name to display.</returns>
        private static string GetName(this Enum type)
        {
            if (NameCache.ContainsKey(type))
            {
                return NameCache[type];
            }

            var enumType = type.GetType();
            var info = enumType.GetField(type.ToString());
            var descriptionAttribute = info.GetCustomAttributes(false).OfType<DescriptionAttribute>().FirstOrDefault();
            var value = string.Empty;
            if (descriptionAttribute != null)
            {
                value = type.ToDescription();
            }
            NameCache.Add(type, value);
            return value;
        }

    }
}